//---------------------------------------------------------------------
//  This file is part of the Background Motion solution.
// 
//  Copyright (C) Mindscape (TM).  All rights reserved.
//  http://www.mindscape.co.nz
// 
//  THIS CODE AND INFORMATION ARE PROVIDED AS IS WITHOUT WARRANTY OF ANY
//  KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
//  PARTICULAR PURPOSE.
//---------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;

using Microsoft.Practices.EnterpriseLibrary.Validation;

using Mindscape.BackgroundMotion.Core.Model;
using Mindscape.BackgroundMotion.Core.Search;
using Mindscape.BackgroundMotion.Core.Utilities;
using Mindscape.BackgroundMotion.Core.Validators;
using Mindscape.BackgroundMotion.Model.Properties;
using Validators_UrlValidator=Mindscape.BackgroundMotion.Core.Validators.UrlValidator;
using System.Data.Linq;
using System.Data.Linq.Mapping;

namespace Mindscape.BackgroundMotion.Model
{
  /// <summary>
  /// Represents a contribution to the system
  /// </summary>
  [Table(Name = "Contribution")]
  public sealed class Contribution : ModelObject, IIndexable
  {
    /// <summary>
    /// Enumeration of the values used to rate content and/or download experience
    /// </summary>
    public enum RatingValue
    {
      Zero = 0,
      One = 1,
      Two = 2,
      Three = 3,
      Four = 4,
      Five = 5
    }

    private int _id;
    private int _contributorId;
    private int? _approvedById;
    private int _contentTypeId;
    private int _views;
    private int _downloads;
    private int _ratingTotal;
    private int _ratingCount;
    private DateTime _addedOn;
    private string _previewImage;
    private string _previewLink;
    private string _downloadLink;
    private int _downloadExperienceCount;
    private int _downloadExperienceTotal;
    private string _title;
    private string _description;
    private decimal? _geoX;
    private decimal? _geoY;

    private EntitySet<Comment> _comments;
    private EntityRef<Member> _approvedBy;
    private EntityRef<ContentType> _contentType;
    private EntityRef<Member> _contributor;
    private EntitySet<ContributionTag> _contributionTags;

    private string _geoXString;
    private string _geoYString;

    /// <summary>
    /// Initializes the relationships for a Contribution
    /// </summary>
    public Contribution()
    {
      _approvedBy = default(EntityRef<Member>);
      _contentType = default(EntityRef<ContentType>);
      _contributor = default(EntityRef<Member>);

      _contributionTags = CreateEntitySet<ContributionTag>(
        (ContributionTag c) => c.Contribution = null,
        (ContributionTag c) => c.Contribution = this);

      _comments = CreateEntitySet<Comment>(
        (Comment c) => c.Contribution = null,
        (Comment c) => c.Contribution = this);

      _addedOn = DateTime.Now;
    }

    /// <summary>
    /// Validates this instance for a correct state
    /// </summary>
    /// <remarks>
    /// This is called by the Enterprise Library Validation framework
    /// </remarks>
    /// <param name="results">A set of ValidationResults indicating validation issues with the current instance</param>
    protected override void Validate(ValidationResults results)
    {
      results.AddAllResults(Validation.ValidateFromAttributes(this));

      if (((GeoX != null) && (GeoY == null))
        || ((GeoY != null) && (GeoX == null)))
      {
        results.AddResult(new ValidationResult(Resources.NeedBothGeoCodes, null, null, null, null));  
      }

      // Ignoring validation as some hosting servives obfiscate urls and redirect to the video files
      //if (ContentTypeId == ContentType.Video && !(DownloadLink.ToLower().EndsWith(".wmv") || DownloadLink.ToLower().EndsWith(".mpg")))
      //{
      //  results.AddResult(new ValidationResult(Resources.InvalidVideoType, null, null, null, null));
      //}

      if (ContentTypeId == ContentType.Image && !(DownloadLink.ToLower().EndsWith(".jpg") || DownloadLink.ToLower().EndsWith(".jpeg") || DownloadLink.ToLower().EndsWith(".gif") || DownloadLink.ToLower().EndsWith(".png")))
      {
        results.AddResult(new ValidationResult(Resources.InvalidImageType, null, null, null, null));
      }

      Require("Contributor", ContributorId, Contributor, results);
      Require("Content Type", ContentTypeId, ContentType, results);
    }

    [Column(Storage = "_id", IsPrimaryKey = true, IsDbGenerated = true)]
    public override int Id
    {
      get { return _id; }
      set
      {
        if (_id != value)
        {
          OnPropertyChanging("Id");
          _id = value;
          OnPropertyChanged("Id");
        }
      }
    }    

    public string Tags
    {
      get 
      {
        StringBuilder tagsStringBuilder = new StringBuilder();

        foreach (ContributionTag tag in ContributionTags)
        {
          tagsStringBuilder.AppendFormat("{0} ", tag.Tag.QuotedValue);
        }

        return tagsStringBuilder.ToString().TrimEnd();
      }
      set
      {
        IList<string> tags = SplitTagString(value);

        if (tags.Count > 0)
        {
          IList<Tag> foundTags;

          using (UnitOfWork.Begin())
          {
            foundTags = Repository<Tag>.FindAll(LinqContainsPredicateBuilder.Build<Tag, string>(tags, "Value"));
          }

          foreach (Tag tag in foundTags)
          {
            ContributionTag contributionTag = new ContributionTag();
            contributionTag.Tag = tag;

            tag.Usages += 1;

            ContributionTags.Add(contributionTag);

            tags.Remove(tag.Value);
          }

          foreach (string newTag in tags)
          {
            if (!Tag.IsBannedTag(newTag))
            {
              ContributionTag contributionTag = new ContributionTag();
              contributionTag.Tag = new Tag();
              contributionTag.Tag.Value = newTag;

              ContributionTags.Add(contributionTag);
            }
          }
        }
      }
    }

    /// <summary>
    /// Helper function used to divide up a Tag string into its constituant word parts
    /// </summary>
    /// <param name="tagString">The raw string of 1-N tags</param>
    /// <returns>A list of individual tag results</returns>
    private static IList<string> SplitTagString(string tagString)
    {
      Regex TagSplitRegex = new Regex(@"""([\w\s']+)""|([\w']+)");

      MatchCollection matchCollection = TagSplitRegex.Matches(tagString);

      List<string> tags = new List<string>();

      if (matchCollection.Count > 0)
      {
        foreach (Match match in matchCollection)
        {
          string match1 = match.Groups[1].Value;
          string match2 = match.Groups[2].Value;

          if (!string.IsNullOrEmpty(match1))
          {
            tags.Add(match1);
          }
          else if (!string.IsNullOrEmpty(match2))
          {
            tags.Add(match2);
          }
        }
      }

      return tags;
    }

    /// <summary>
    /// Helper function to return the current RatingValue based on a total and a count
    /// </summary>
    private static RatingValue GetRating(int total, int count)
    {
      if (count == 0)
      {
        return RatingValue.Zero;
      }

      return (RatingValue)Enum.Parse(typeof(RatingValue), Convert.ToInt32(total / count).ToString());
    }

    [Column(Storage = "_contributorId")]
    public int ContributorId
    {
      get { return _contributorId; }
      set
      {
        if ((_contributorId != value))
        {
          OnPropertyChanging("ContributorId");
          _contributorId = value;
          OnPropertyChanged("ContributorId");
        }
      }
    }

    [Column(Storage = "_approvedById")]
    public Nullable<int> ApprovedById
    {
      get { return _approvedById; }
      set
      {
        if ((_approvedById != value))
        {
          OnPropertyChanging("ApprovedById");
          _approvedById = value;
          OnPropertyChanged("ApprovedById");
        }
      }
    }

    [Column(Storage = "_contentTypeId")]
    public int ContentTypeId
    {
      get { return _contentTypeId; }
      set
      {
        if ((_contentTypeId != value))
        {
          OnPropertyChanging("ContentTypeId");
          _contentTypeId = value;
          OnPropertyChanged("ContentTypeId");
        }
      }
    }

    [Column(Storage = "_views")]
    public int Views
    {
      get { return _views; }
      set
      {
        if ((_views != value))
        {
          OnPropertyChanging("Views");
          _views = value;
          OnPropertyChanged("Views");
        }
      }
    }

    [Column(Storage = "_downloads")]
    public int Downloads
    {
      get { return _downloads; }
      set
      {
        if ((_downloads != value))
        {
          OnPropertyChanging("Downloads");
          _downloads = value;
          OnPropertyChanged("Downloads");
        }
      }
    }

    [Column(Storage = "_ratingTotal")]
    public int RatingTotal
    {
      get { return _ratingTotal; }
      set
      {
        if ((_ratingTotal != value))
        {
          OnPropertyChanging("RatingTotal");
          _ratingTotal = value;
          OnPropertyChanged("RatingTotal");
        }
      }
    }

    [Column(Storage = "_ratingCount")]
    public int RatingCount
    {
      get { return _ratingCount; }
      set
      {
        if ((_ratingCount != value))
        {
          OnPropertyChanging("RatingCount");
          _ratingCount = value;
          OnPropertyChanged("RatingCount");
        }
      }
    }

    [RequireDateTimeValidator("Added On")]
    [Column(Storage = "_addedOn")]
    public DateTime AddedOn
    {
      get { return _addedOn; }
      set
      {
        if ((_addedOn != value))
        {
          OnPropertyChanging("AddedOn");
          _addedOn = value;
          OnPropertyChanged("AddedOn");
        }
      }
    }

    [RequireValidator("Preview Image", 1024)]
    [UrlValidator("Preview Image")]
    [Column(Storage = "_previewImage")]
    public string PreviewImage
    {
      get { return _previewImage; }
      set
      {
        if ((_previewImage != value))
        {
          OnPropertyChanging("PreviewImage");
          _previewImage = value;
          OnPropertyChanged("PreviewImage");
        }
      }
    }

    [RequireValidator("Preview Link", 1024)]
    [UrlValidator("Preview Link")]
    [Column(Storage = "_previewLink")]
    public string PreviewLink
    {
      get { return _previewLink; }
      set
      {
        if ((_previewLink != value))
        {
          OnPropertyChanging("PreviewLink");
          _previewLink = value;
          OnPropertyChanged("PreviewLink");
        }
      }
    }

    [RequireValidator("Download Link", 1024)]
    [UrlValidator("Download Link")]
    [Column(Storage = "_downloadLink")]
    public string DownloadLink
    {
      get { return _downloadLink; }
      set
      {
        if ((_downloadLink != value))
        {
          OnPropertyChanging("DownloadLink");
          _downloadLink = value;
          OnPropertyChanged("DownloadLink");
        }
      }
    }

    [Column(Storage = "_downloadExperienceCount")]
    public int DownloadExperienceCount
    {
      get { return _downloadExperienceCount; }
      set
      {
        if ((_downloadExperienceCount != value))
        {
          OnPropertyChanging("DownloadExperienceCount");
          _downloadExperienceCount = value;
          OnPropertyChanged("DownloadExperienceCount");
        }
      }
    }

    [Column(Storage = "_downloadExperienceTotal")]
    public int DownloadExperienceTotal
    {
      get { return _downloadExperienceTotal; }
      set
      {
        if ((_downloadExperienceTotal != value))
        {
          OnPropertyChanging("DownloadExperienceTotal");
          _downloadExperienceTotal = value;
          OnPropertyChanged("DownloadExperienceTotal");
        }
      }
    }

    [RequireValidator("Title", 50)]
    [Column(Storage = "_title")]
    public string Title
    {
      get { return _title; }
      set
      {
        if ((_title != value))
        {
          OnPropertyChanging("Title");
          _title = value;
          OnPropertyChanged("Title");
        }
      }
    }

    [RequireValidator("Description", 255)]
    [Column(Storage = "_description")]
    public string Description
    {
      get { return _description; }
      set
      {
        if ((_description != value))
        {
          OnPropertyChanging("Description");
          _description = value;
          OnPropertyChanged("Description");
        }
      }
    }

    [Column(Storage = "_geoX")]
    public decimal? GeoX
    {
      get { return _geoX; }
      set
      {
        if ((_geoX != value))
        {
          OnPropertyChanging("GeoX");
          _geoX = value;
          OnPropertyChanged("GeoX");
        }
      }
    }

    [Column(Storage = "_geoY")]
    public decimal? GeoY
    {
      get { return _geoY; }
      set
      {
        if ((_geoY != value))
        {
          OnPropertyChanging("GeoY");
          _geoY = value;
          OnPropertyChanged("GeoY");
        }
      }
    }

    [Association(Storage = "_comments", OtherKey = "ContributionId")]
    public EntitySet<Comment> Comments
    {
      get { return _comments; }
      set { _comments.Assign(value); }
    }

    [Association(Storage = "_approvedBy", ThisKey = "ApprovedById", IsForeignKey=true)]
    public Member ApprovedBy
    {
      get { return _approvedBy.Entity; }
      set
      {
        UpdateAssociation("ApprovedBy", ref _approvedBy, value,
          (Member c) => c.ApprovedContributions.Remove(this),
          (Member c) => c.ApprovedContributions.Add(this));
      }
    }

    [Association(Storage = "_contentType", ThisKey = "ContentTypeId", IsForeignKey = true)]
    public ContentType ContentType
    {
      get { return _contentType.Entity; }
      set
      {
        UpdateAssociation("ContentType", ref _contentType, value,
          (ContentType c) => c.Contributions.Remove(this),
          (ContentType c) => c.Contributions.Add(this));
      }
    }

    [Association(Storage = "_contributor", ThisKey = "ContributorId", IsForeignKey = true)]
    public Member Contributor
    {
      get { return _contributor.Entity; }
      set
      {
        UpdateAssociation("Contributor", ref _contributor, value,
          (Member c) => c.Contributions.Remove(this),
          (Member c) => c.Contributions.Add(this));
      }
    }

    [Association(Storage = "_contributionTags", OtherKey = "ContributionId")]
    public EntitySet<ContributionTag> ContributionTags
    {
      get { return _contributionTags; }
      set { _contributionTags.Assign(value); }
    }

    public string SearchData
    {
      get
      {
        StringBuilder searchData = new StringBuilder();
        
        searchData.Append( string.Join(" ", new string[]
          {
            Title,
            Description,
            Contributor.Username,
            ContentType.Description          
          }
        ));

        foreach (Comment comment in Comments)
        {
          searchData.Append(" ");
          searchData.Append(string.Join(" ", new string[]
            {
              comment.Subject,
              comment.Message
            }
          ));
        }

        return searchData.ToString();
      }
    }

    /// <summary>
    /// Adds a new rating to this instance
    /// </summary>
    public void AddRating(RatingValue rating)
    {
      RatingTotal += (int)rating;
      RatingCount++;
    }

    /// <summary>
    /// Adds a new rating to the download experience for this instance
    /// </summary>
    public void AddDownloadExperienceRating(RatingValue rating)
    {
      DownloadExperienceTotal += (int)rating;
      DownloadExperienceCount++;
    }

    /// <summary>
    /// The current rating of this contribution
    /// </summary>
    public RatingValue Rating
    {
      get { return GetRating(RatingTotal, RatingCount); }
    }

    /// <summary>
    /// Indicates if the contribution has geo-spatial values and can be rendered on a map
    /// </summary>
    public bool CanRenderMap
    {
      get
      {
        if (GeoX.HasValue & GeoY.HasValue)
        {
          return true;
        }
        else
        {
          return false;
        }
      }
    }

    /// <summary>
    /// Returns the link to the VirtualEarth view of the contribution
    /// </summary>
    /// <remarks>
    /// This should be moved out of the model and into a seperate class, not SRP
    /// </remarks>
    public string VirtualEarthLink
    {
      get
      {
        if (CanRenderMap)
        {
          // TODO: this is not a model concern
          return string.Format("/VirtualEarth.aspx?geox={0}&geoy={1}", GeoX.Value, GeoY.Value);
        }

        return string.Empty;
      }
    }

    /// <summary>
    /// The current rating of the download experience for this content
    /// </summary>
    public RatingValue DownloadExperienceRating
    {
      get { return GetRating(DownloadExperienceTotal, DownloadExperienceCount); }
    }

    /// <summary>
    /// The abridged version of the title for this instance
    /// </summary>
    public string SummaryTitle
    {
      get
      {
        if (Title.Length > 20)
          return Title.Substring(0, 16) + "...";
        else
          return Title;
      }
    }

    [DecimalValidator("GeoX")]
    public string GeoXString
    {
      get { return _geoXString; }
      set
      {
        _geoXString = value;

        GeoX = Decimal.Parse(_geoXString);
      }
    }

    [DecimalValidator("GeoY")]
    public string GeoYString
    {
      get { return _geoYString; }
      set
      {
        _geoYString = value;

        GeoY = Decimal.Parse(_geoYString);
      }
    }
  }
}